package net.esj.basic.core.engine.classloader;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;

import net.esj.basic.utils.Assert;

public class DyncClassLoader extends ClassLoader {

	private URI extBasicUri;
	
	private Set<URI> loadedUri = new HashSet<URI>();

	public DyncClassLoader(String extBasicPath) throws URISyntaxException {
		this(new URI(extBasicPath));
	}
	
	public DyncClassLoader(URI extBasicUri) throws URISyntaxException {
		super();
		Assert.notNull(extBasicUri,"基础加载路径不能为NULL");
		this.extBasicUri = extBasicUri;
		initLoadedUrl();
	}

	public DyncClassLoader(ClassLoader parent,String extBasicPath) throws URISyntaxException {
		this(parent,new URI(extBasicPath));
	}
	
	public DyncClassLoader(ClassLoader parent, URI extBasicUri) throws URISyntaxException {
		super(parent);
		Assert.notNull(extBasicUri,"基础加载路径不能为NULL");
		this.extBasicUri = extBasicUri;
		initLoadedUrl();
	}

	private void initLoadedUrl() throws URISyntaxException{
		for(File f:  new File(extBasicUri.toString()).listFiles()){
			if(f.isDirectory()){
				loadedUri.add(f.toURI());
			}
		}
	}
	
	@Override
	public Class<?> loadClass(String name) throws ClassNotFoundException {
		return this.loadClass(name, false);
	}

	@Override
	protected Class<?> loadClass(String name, boolean b)
			throws ClassNotFoundException {
		Class clazz = null;
		try {
			clazz = super.loadClass(name, b);
			if (clazz != null) {
				return clazz;
			}
		} catch (ClassNotFoundException e) {
		}
		int dotIndex = name.indexOf(".");
		String separator = File.separator;
		if (separator.equals("\\")) {
			separator = "\\\\";
		}
		String fileName;

		if (dotIndex != -1) {
			fileName = new StringBuilder().append(
					name.replaceAll("\\.", separator)).append(".class")
					.toString();
		} else {
			fileName = new StringBuilder().append(name).append(".class")
					.toString();
		}
		InputStream is = getLocalResourceAsStream(fileName);
		if (is == null) {
			throw new ClassNotFoundException(name);
		}

		try {
			Class loadedClass = readClass(name, is);
			is.close();
			if (loadedClass != null) {
				return loadedClass;
			} else {
				throw new ClassNotFoundException(name);
			}

		} catch (Exception e) {
			throw new ClassNotFoundException(name);
		} finally {
			try {
				is.close();
			} catch (IOException e) {
			}
		}
	}

	private Class readClass(String className, InputStream is)
			throws IOException {
		BufferedInputStream bis = new BufferedInputStream(is);
		ByteArrayOutputStream os = new ByteArrayOutputStream();
		byte[] bytes = new byte[1024 * 10];

		int readBytes;

		while ((readBytes = bis.read(bytes)) != -1) {

			os.write(bytes, 0, readBytes);

		}

		byte[] b = os.toByteArray();

		return defineClass(className, b, 0, b.length);

	}

	private URL getLocalResource(String name) {
		for (URI uri : loadedUri) {
			File entry = new File(uri);
			if (entry.isDirectory() && entry.exists()) {
				File f = new File(entry, name);
				if (f.exists()) {
					URL url;
					try {
						url = f.toURI().toURL();
					} catch (MalformedURLException ex) {
						continue;
					}
					return url;
				}

			} else if (entry.isFile() && entry.exists()) {
				URL url = null;
				try {
					ZipFile zf = new ZipFile(entry);
					name = name.replaceAll("\\\\", "/");
					ZipEntry zipEntry = zf.getEntry(name);
					if (zipEntry == null) {
						continue;
					}
					String url_0_ = entry.getAbsolutePath().replaceAll("\\\\",
							"/");
					if (!url_0_.startsWith("/")) {
						url_0_ = new StringBuilder().append("/").append(url_0_)
								.toString();
					}
					url = new URL(new StringBuilder().append("jar:file://")
							.append(url_0_).append("!/").append(name)
							.toString());
				} catch (ZipException zipexception) {
					zipexception.printStackTrace();
				} catch (IOException ioexception) {
					ioexception.printStackTrace();
				}
				return url;
			}

		}
		return null;

	}

	private InputStream getLocalResourceAsStream(String name) {
		URL res = getLocalResource(name);
		if (res == null) {
			return null;
		}
		InputStream inputstream = null;
		try {
			inputstream = res.openStream();
		} catch (IOException ex) {
			ex.printStackTrace();
		}
		return inputstream;
	}

	public InputStream getResourceAsStream(String name) {
		InputStream _super = super.getResourceAsStream(name);
		return _super != null ? _super : getLocalResourceAsStream(name);
	}

	public URL getResource(String name) {
		URL resource = super.getResource(name);
		return resource != null ? resource : getLocalResource(name);
	}

}
